package belf.migrate.engine.job.verify;

import belf.migrate.api.exception.CatalogException;
import belf.migrate.api.table.schema.AbstractJdbcCatalog;
import belf.migrate.api.table.schema.AbstractSinkCatalog;
import belf.migrate.api.taskconf.JobContext;
import belf.migrate.api.type.DbObjectType;
import belf.migrate.api.type.LogLevelType;
import belf.migrate.api.util.PostLog;
import belf.migrate.api.util.VerifyObjectTypes;
import belf.migrate.core.number.NumberFormatUtil;
import belf.migrate.api.job.Job;
import belf.migrate.engine.job.model.verify.VerifyBaseModel;
import belf.migrate.engine.job.model.verify.VerifyTriggerResult;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import static belf.migrate.api.job.JobCode.*;

@Slf4j
public class VerifyTriggerTool {

    private Job job;

    private JobContext jobContext;

    private AbstractJdbcCatalog sourceCatalog;

    private AbstractSinkCatalog sinkCatalog;

    public VerifyTriggerTool(JobContext jobContext, Job job) {
        this.jobContext = jobContext;
        this.sourceCatalog = jobContext.getSourceCatalog();
        this.sinkCatalog = jobContext.getSinkCatalog();
        this.job = job;
    }

    public VerifyTriggerResult run() {

        if (!VerifyObjectTypes.isSupport(job.getTask().getTaskConf().getSourceConf().getCatalogName(),
                job.getTask().getTaskConf().getSinkConf().getCatalogName(), DbObjectType.TRIGGER)) {
            log.info("当前数据库不支持触发器校验!");
            return null;
        }

        log.info("开始进行触发器校验!");
        PostLog.sendLogModel(
                job.getJobId(),
                job.getTask().getTaskType().getName(),
                "开始进行触发器校验!",
                LogLevelType.INFO.getType(),
                EXECUTING,
                jobContext.getProgress());

        VerifyTriggerResult triggerResult = new VerifyTriggerResult();
        //获取数据库信息
        String sourceDatabaseName = job.getTask().getTaskConf().getSourceConf().getDatabaseName();
        String sinkDatabaseName = job.getTask().getTaskConf().getSinkConf().getDatabaseName();
        String sourceSchemaName = job.getTask().getTaskConf().getSourceConf().getSchemaName();
        String sinkSchemaName = job.getTask().getTaskConf().getSinkConf().getSchemaName();
        try {
            //获取源库所有触发器
            List<String> sourceTriggerNameList = sourceCatalog.listTriggerNames(sourceDatabaseName, sourceSchemaName);
            List<String> sinkTriggerNameList = sinkCatalog.listTriggerNames(sinkDatabaseName, sinkSchemaName);
            //判断源库触发器是否为空
            if (sourceTriggerNameList == null) {
                if (sinkTriggerNameList == null) {
                    log.info("源数据库和目标数据库触发器均为空!");
                    PostLog.sendLogModel(
                            job.getJobId(),
                            job.getTask().getTaskType().getName(),
                            "源数据库和目标数据库触发器均为空!",
                            LogLevelType.INFO.getType(),
                            EXECUTING,
                            jobContext.getProgress());
                    triggerResult.setCode(SUCCESS);
                    triggerResult.setAllSize(0);
                    triggerResult.setSuccessCount(0);
                    triggerResult.setFailCount(0);
                    triggerResult.setSuccessPercent("100.0%");
                    triggerResult.setFailPercent("0.0%");
                    triggerResult.setSuccess(true);
                } else {
                    log.info("源数据库触发器为空，目标数据库触发器不为空!");
                    PostLog.sendLogModel(
                            job.getJobId(),
                            job.getTask().getTaskType().getName(),
                            "源数据库触发器为空，目标数据库触发器不为空!",
                            LogLevelType.ERROR.getType(),
                            EXECUTING,
                            jobContext.getProgress());
                    triggerResult.setCode(FAILTURE);
                    triggerResult.setAllSize(0);
                    triggerResult.setSuccessCount(0);
                    triggerResult.setFailCount(0);
                    triggerResult.setSuccessPercent("0.0%");
                    triggerResult.setFailPercent("100.0%");
                    triggerResult.setSuccess(false);
                }

            } else {
                int allSize = sourceTriggerNameList.size();
                int successCount = 0;
                int failCount = 0;
                Map<String, String> sourceLowerTriggerNameMap = sourceTriggerNameList.stream().collect(
                        Collectors.toMap(s -> s.toLowerCase(Locale.ROOT),
                                s -> s));
                Map<String, String> sinkLowerTriggerNameMap = null;
                if (sinkTriggerNameList != null) {
                    sinkLowerTriggerNameMap = sinkTriggerNameList.stream().collect(
                            Collectors.toMap(s -> s.toLowerCase(Locale.ROOT),
                                    s -> s));
                }
                List<VerifyBaseModel> triggerBaseModelList = new ArrayList<>();
                int index = 0;
                //遍历源库触发器
                for (String sourceTriggerName : sourceLowerTriggerNameMap.keySet()) {
                    jobContext.addProgress(DbObjectType.TRIGGER.getTypeName(), allSize);
                    long startTime = System.currentTimeMillis();
                    VerifyBaseModel triggerBaseModel = new VerifyBaseModel();
                    if (sinkLowerTriggerNameMap != null && sinkLowerTriggerNameMap.containsKey(sourceTriggerName)) {
                        triggerBaseModel.setSuccess(true);
                        triggerBaseModel.setSourceName(sourceLowerTriggerNameMap.get(sourceTriggerName));
                        triggerBaseModel.setSinkName(sinkLowerTriggerNameMap.get(sourceTriggerName));
                        successCount++;
                    } else {
                        triggerBaseModel.setSuccess(false);
                        triggerBaseModel.setSourceName(sourceLowerTriggerNameMap.get(sourceTriggerName));
                        failCount++;
                    }
                    //计算耗时
                    long endTime = System.currentTimeMillis();
                    String timeConsuming = String.valueOf((double) (endTime - startTime) / 1000);
                    triggerBaseModel.setTimeConsuming(String.format("%s秒", timeConsuming));
                    triggerBaseModelList.add(triggerBaseModel);

                    index++;
                    PostLog.sendLogModel(
                            job.getJobId(),
                            job.getTask().getTaskType().getName(),
                            String.format("触发器 %s 校验完成，目前进度：%s / %s.", sourceTriggerName, index, allSize),
                            LogLevelType.INFO.getType(),
                            EXECUTING,
                            jobContext.getProgress());
                }
                triggerResult.setSuccess(successCount == allSize);
                triggerResult.setAllSize(allSize);
                triggerResult.setSuccessCount(successCount);
                triggerResult.setFailCount(failCount);
                float successPercent = allSize == 0 ? 1 : (float)successCount / allSize;
                float failPercent = allSize == 0 ? 0 : (float)failCount / allSize;
                triggerResult.setSuccessPercent(NumberFormatUtil.numberToString(successPercent, "0.0%"));
                triggerResult.setFailPercent(NumberFormatUtil.numberToString(failPercent, "0.0%"));
                triggerResult.setObjectList(triggerBaseModelList);

            }

        } catch (CatalogException e) {
            log.error("触发器校验失败！", e);
            PostLog.sendLogModel(
                    job.getJobId(),
                    job.getTask().getTaskType().getName(),
                    String.format("触发器校验失败: %s", e.getMessage()),
                    LogLevelType.ERROR.getType(),
                    EXECUTING,
                    jobContext.getProgress());
            triggerResult.setCode(FAILTURE);
        }
        return triggerResult;
    }

}
